﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;

namespace Chisel.Core
{

    public class DiagramCanvas : Canvas
    {
        public DiagramCanvas() : base()
        {

			Background = new SolidColorBrush(Colors.White);

            Actors = new ActorCollection<Actor>();


            StyleProvider = new StyleProvider();
            
            this.MouseLeftButtonDown += OnMouseLeftButtonDown;
            this.MouseLeftButtonUp += OnMouseLeftButtonUp;
            this.MouseMove += OnMouseMove;

            this.Loaded += OnLoaded;

			_grid = new DiagramGrid();
			Children.Add(_grid);

			SnapToGrid = true;

			_textField = new TextField();
			Children.Add(_textField);
			Canvas.SetZIndex(_textField, 1000);
        }

        /// <summary>
        /// This gets called after all the children have been initialized.
        /// </summary>
        private void OnLoaded(object sender, RoutedEventArgs e)
        {
            // add any existing actors
            foreach (var child in Children)
            {
                if (child is Actor)
                    Actors.Add(child as Actor);
            } 
        }

        /// <summary>
        /// The actors in the canvas.
        /// </summary>
        /// <remarks>Also exposes the selected actors.</remarks>
        public ActorCollection<Actor> Actors { get; private set; }

        /// <summary>
        /// The style provider used by the canvas.
        /// </summary>
        public StyleProvider StyleProvider { get; private set; }

        /// <summary>
        /// The rubber band used for selection.
        /// </summary>
        private RubberBand rubberBand;

        void OnMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            if (!e.Handled)
            {
                rubberBand = new RubberBand(this, e.GetPosition(this));
            }
        }
        
        void OnMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
        {
            Actors.EndDrag(e);
            if (!e.Handled)
            {
                Actors.DeselectAll();
            }

            if (rubberBand != null)
            {
                rubberBand.SelectContents();
                Children.Remove(rubberBand);
                rubberBand = null;
            }
        }
        
        void OnMouseMove(object sender, MouseEventArgs e)
        {
            Actors.Drag(e);

            if (rubberBand != null)
                rubberBand.Stop = e.GetPosition(this);
		}

		/// <summary>
		/// Gets all actors hit by the given mouse event.
		/// </summary>
		public IList<Actor> GetHits(MouseEventArgs e)
		{
			var hits = new List<Actor>();
			var pos = e.GetPosition(this);
			foreach (var actor in Actors.All)
			{
				if (actor.HitTest(pos))
					hits.Add(actor);
			}
			return hits;
		}

		/// <summary>
		/// Gets the top-most actor that is hit by the mouse event.
		/// </summary>
		public Actor GetTopHit(MouseEventArgs e)
		{
			Actor top = null;
			int zTop = -1;
			foreach (var hit in GetHits(e))
			{
				var z = Canvas.GetZIndex(hit);
				if (z > zTop)
				{
					top = hit;
					zTop = z;
				}
			}
			return top;
		}


		#region Position Validation

		/// <summary>
		/// Ensures that the given point follows all of the layout rules of the canvas.
		/// </summary>
		/// <remarks>Checks the grid if SnapToGrid is true and checks the bounds.</remarks>
		public Point ValidatePoint(Point point)
		{
			var newPoint = point;
			newPoint.X = Math.Max(newPoint.X, 0);
			newPoint.Y = Math.Max(newPoint.Y, 0);
			newPoint.X = Math.Min(newPoint.X, ActualWidth);
			newPoint.Y = Math.Min(newPoint.Y, ActualHeight);
			if (SnapToGrid)
				newPoint = _grid.SnapPoint(newPoint);
			return newPoint;
		}

		/// <summary>
		/// Validates the actor size using ValidatePoint().
		/// </summary>
		public void ValidateActorPosition(Actor actor)
		{
			actor.Position = ValidatePoint(actor.Position);
			var pos = actor.Position;
			var edge = new Point(pos.X + actor.ActualWidth, pos.Y + actor.ActualHeight);
			edge = ValidatePoint(edge);
			actor.Position = ValidatePoint(new Point(edge.X - actor.ActualWidth, edge.Y - actor.ActualHeight));
		}

		/// <summary>
		/// Validates the actor position using ValidatePoint() and its minimum size.
		/// </summary>
		public void ValidateActorSize(Actor actor)
		{
			var pos = actor.Position;
			var edge = new Point(pos.X + actor.Width, pos.Y + actor.Height);
			edge = ValidatePoint(edge);
			actor.Width = Math.Max(edge.X - pos.X, actor.MinWidth);
			actor.Height = Math.Max(edge.Y - pos.Y, actor.MinHeight);
		}

		#endregion


		#region The Grid

		/// <summary>
		/// If true, all moves and resizes must snap to the grid.
		/// </summary>
		public bool SnapToGrid { get; set; }

		/// <summary>
		/// The grid.
		/// </summary>
		private DiagramGrid _grid;

		#endregion


		#region Text Entry

		private TextField _textField;


		/// <summary>
		/// Shows a text box at pos with the given size.
		/// </summary>
		/// <param name="pos">The position of the text box.</param>
		/// <param name="size">The size of the text box.</param>
		/// <param name="initial">The initial text.</param>
		/// <param name="handler">The handler to call when the user wants to change the text.</param>
		public void EditText(Point pos, Point size, string initial, TextChangedHandler handler)
		{
			_textField.Show(pos, size, initial, handler);
		}

		#endregion

	}
}
